//(function(){})();立即执行函数，闭包，避免污染全局变量
//通常一个插件只暴露一个变量给全局供其它程序调用
;(function(global) {

    //开启严格模式，规范代码，提高浏览器运行效率
    "use strict";

    let def = {//一些默认参数
        border: 'none',//是否显示边框，默认不显示
    };

    let _this;//保留指向自身的指针，方便原型链以外的方法调用

    // 定义一个类/方法，通常首字母大写
    let Myvue = function(options) {
        _this = this;
        this._data = options.data;
        this._el = options.el;
        this.$el = this._templateDOM = document.querySelector(this._el);
        this._parent = this._templateDOM.parentNode;
        this.options = {...def, ...options};//合并属性
        this.init();//初始化
    };

    // 覆写原型链，给继承者提供方法
    Myvue.prototype = {
        init: function() {
            // 把data中的属性变成响应式的
            Object.keys(_this._data).forEach((key) => {
                convertReactive(_this._data, key, _this._data[key])
            })
            render()
        },
    };


    /**
     * 实现v-text和v-model
     * @param node
     */
    function viewModel(node) {
        const attrs = node.attributes
        Array.from(attrs).forEach(attr => {
            const nodeName = attr.nodeName
            const nodeValue = attr.nodeValue
            // 实现v-text
            if (nodeName === 'v-text') {
                node.innerText = _this._data[nodeValue]
                // 收集更新函数
                Dep.collect(nodeValue, () => {
                    //console.log(`当前您修改了属性${nodeValue}`)
                    node.innerText = _this._data[nodeValue]
                })
            }
            // 实现v-model
            if (nodeName === 'v-model') {
                // 调用dom操作给input标签绑定数据
                node.value = _this._data[nodeValue]

                // 监听input事件 在事件回调中 拿到最新的输入值 赋值给绑定的属性
                node.addEventListener('input', (e) => {
                    // 反向赋值
                    console.log("input监听器执行，此处会导致data内数据变动，触发defineProperty的set方法")
                    _this._data[nodeValue] = e.target.value
                })
                // 收集更新函数
                Dep.collect(nodeValue,()=>{
                    node.value = _this._data[nodeValue]
                })
            }
        })
    }

    // 引入发布订阅模式
    const Dep = {
        map: {},
        // 收集事件的方法
        collect(eventName, fn) {
            /*            console.log("collect")
                        console.log("eventName",eventName)
                        console.log("fn",fn)*/
            // 如果当前map中已经初始化好了 click:[]
            // 就直接往里面push  如果没有初始化首次添加  就先进行初始化
            if (!this.map[eventName]) {
                this.map[eventName] = []
            }
            this.map[eventName].push(fn)
        },
        // 触发事件的方法
        trigger(eventName) {
            this.map[eventName].forEach(fn => fn())
        }
    }

    /**
     * 将数据变成响应式数据
     * @param data 原数据
     * @param key 转换的key
     * @param value 转换的value
     * @returns {*}
     */
    function convertReactive(data, key, value) {
        // 进行转换操作
        Object.defineProperty(data, key, {
            get() {
                return value
            },
            set(newValue) {
                console.log("convertReactive 的set方法执行")
                // 如果新旧值相等，不执行后续逻辑
                if (newValue === value) {
                    return
                }
                // 把最新的值 反映到视图中  这里是关键的位置
                value = newValue
                // 核心：操作dom  就是通过操作dom api 把最新的值设置上去
                // 在这里进行精准更新 -> 通过data中的属性名找到对应的更新函数依次执行
                Dep.trigger(key)
            }
        })
    }


    /**
     * 渲染
     */
    function render() {
        // 拷贝原来的DOM元素赋值给新元素
        let realDOM = _this._templateDOM.cloneNode(true);
        // 调用模拟插值表达式
        compiler(realDOM, _this._data);
        update(realDOM)
    }

    /**
     * 模拟插值表达式:把{{ }}内的字符串替换为Myvue中data对象中的数据
     * @param el
     * @param data
     */
    function compiler(el, data) {
        //正则表达式  .+匹配{{}}所有字符 ？取消贪婪  g全局
        let regulation = /\{\{(.+?)\}\}/g;

        let childEle = el.childNodes;
        for (var i = 0; i < childEle.length; i++) {
            let type = childEle[i].nodeType;
            //节点nodeType==1 为标签元素
            //节点nodeType==3 为文本元素
            if (type === 3) {
                let txt = childEle[i].nodeValue;
                txt = txt.replace(regulation, function (_, val) {
                    // 此函数的第0个参数为匹配到的内容，例如 {{name}}
                    // 第n个参数为第n组内容，例如 name
                    let key = val.trim();
                    //调用 对象嵌套函数
                    return getValueByPath(data, key);
                })
                childEle[i].nodeValue = txt;
            } else if (type === 1) {
                viewModel(childEle[i])
                compiler(childEle[i], data)
            }
        }
    }

    /**
     * 更新真实dom
     * @param real
     */
    function update(real) {
        //用新的元素替换原来的DOM元素
        _this._parent.replaceChild(real, _this.$el)
    }

    /**
     * 通过遍历key获得数据
     * @param obj
     * @param str
     * @returns {*}
     */
    function getValueByPath(obj, str) {
        let paths = str.split('.');
        //console.log("paths",paths)
        // 定义变量把对象的值赋给它
        let res = obj;
        //console.log("res",res)

        //定义变量  让他等于删除数组中第一个元素
        let prop;

        // shift() 删除数组中第一个元素，返回值为删除的元素
        while (prop = paths.shift()) {
            //循环获取 下一层获取对象元素 的值  obj.second.username
            res = res[prop]
            //console.log("prop =",prop,",res =",res)
        }

        return res
    }

    /*    //兼容CommonJs规范
        if (typeof module !== 'undefined' && module.exports) module.exports = MyPlugin;

        //兼容AMD/CMD规范
        if (typeof define === 'function') define(function() { return MyPlugin; });*/

    //注册全局变量
    global.Myvue = Myvue;

//this，在浏览器环境指window，在nodejs环境指global
//使用this而不直接用window/global是为了兼容浏览器端和服务端
//将this传进函数体，使全局变量变为局部变量，可缩短函数访问全局变量的时间
})(this);
